//	Copyright (C) 2007 Christian Martick, http://juhu-mac.blogspot.com
//
//	Permission is hereby granted, free of charge, to any person obtaining a copy
//	of this software and associated documentation files (the "Software"), to deal
//	in the Software without restriction, including without limitation the rights
//	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//	copies of the Software, and to permit persons to whom the Software is
//	furnished to do so, subject to the following conditions:
//
//	The above copyright notice and this permission notice shall be included in
//	all copies or substantial portions of the Software.
//
//	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//	THE SOFTWARE.

#import "ApplicationController.h"

@implementation ApplicationController

/* 
	private tools
*/

OSStatus hotkeyHandler(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData)
{
	//called by hotkey handler (installed below) userData is self (given in setup)
	[(id) userData statusItemClick: nil];
	return noErr;
}

- (void) _growlConfig
{
	//set up growl stuff
	if (!growler)
	{
		if (([GrowlApplicationBridge isGrowlInstalled]) && [GrowlApplicationBridge isGrowlRunning])
		{
			growler = YES;
			[GrowlApplicationBridge setGrowlDelegate: self];
		} else
			growler = NO;
	}
}

- (void) _growlUpdateWithIndexSet: (NSIndexSet *) indexes
{
	// upate growl
	// show the newest five or less
	if (growler)
	{
		NSData *data;
		NSString *title;
		NSString *type;
		
		unsigned int indexTmp = [indexes firstIndex];

		for (int i = 0; (i < 5) && (indexTmp != NSNotFound); i++)
		{	
			// skip own posts and channels if hidden	
			if (![[[[jaiku stream: streamSelector] objectAtIndex: indexTmp] objectForKey: kSmcEntryKeyNick] isEqualToString: 
				[jaiku username]] && 
				(![[[[jaiku stream: streamSelector] objectAtIndex: indexTmp] objectForKey: kSmcEntryKeyChannelFlag] boolValue] ||
					![[NSUserDefaults standardUserDefaults] boolForKey: @"hideChannels"]))
			{
		
				data = [NSData dataWithData: [[jaiku getImageForUser: 
											   [[[jaiku stream: streamSelector] objectAtIndex: indexTmp] objectForKey: kSmcEntryKeyNick]] TIFFRepresentation]];

				
				if ([[[[[jaiku stream: streamSelector] objectAtIndex: indexTmp] objectForKey: kSmcEntryKeyURL] absoluteString] 
					rangeOfString: @"#c-"].location != NSNotFound)
				{
					//is a comment
					title = [[[jaiku stream: streamSelector] objectAtIndex: indexTmp] objectForKey: kSmcEntryKeyFirstName];
					type = grNOTIFICATION_NEW_COMMENT;
				} else
				{
					//not a comment
					title = [NSString stringWithFormat: @"%@ (%@)",
						[[[jaiku stream: streamSelector] objectAtIndex: indexTmp] objectForKey: kSmcEntryKeyFirstName],
						[[[jaiku stream: streamSelector] objectAtIndex: indexTmp] objectForKey: kSmcEntryKeyNick]];
					type = grNOTIFICATION_NEW_POST;
				}
				
				[GrowlApplicationBridge
					notifyWithTitle: title
					description: [[[jaiku stream: streamSelector] objectAtIndex: indexTmp] objectForKey: kSmcEntryKeyTitle]
					notificationName: type
					iconData: data
					priority: 0
					isSticky: NO
					clickContext: [NSDictionary dictionaryWithObjectsAndKeys: 
						[[[jaiku stream: streamSelector] objectAtIndex: indexTmp] objectForKey: kSmcEntryKeyID], @"id",
						[NSNumber numberWithInt: indexTmp], @"item", NULL]];
			}
			indexTmp = [indexes indexGreaterThanIndex: indexTmp];
		}
	}
}

- (void) _updateChatClients: (NSString *) text
{
	//updates ichat, adium and skype status
	//using applescript, might be slow but it works
	
	NSMutableString *status = [NSMutableString stringWithString: text];
	NSDictionary *error;
	
	[status replaceOccurrencesOfString: @"\"" 
		withString: @"'" options: NSBackwardsSearch 
		range: NSMakeRange( 0, [status length])];
	
	//iChat
	NSAppleScript *running = [[[NSAppleScript alloc] initWithSource: 
			@"\
			tell application \"System Events\"\n\
			 set ret to count (every process whose name is \"iChat\")\n\
			end tell"] autorelease];
			
	if ([[running executeAndReturnError: &error] booleanValue])
	{
		//ichat is running
		running = [[[NSAppleScript alloc] initWithSource: 
		[NSString stringWithFormat: 
			@"tell application \"iChat\" \n\
			   set status message to \"%@\" \n\
			  end tell", status]] autorelease];
		[running executeAndReturnError: &error];
	}
		
	//Adium
	running = [[[NSAppleScript alloc] initWithSource: 
			@"\
			tell application \"System Events\"\n\
			 set ret to count (every process whose name is \"Adium\")\n\
			end tell"] autorelease];
	
	if ([[running executeAndReturnError: &error] booleanValue])
	{
		//adium is running
		running = [[[NSAppleScript alloc] initWithSource: 
		[NSString stringWithFormat: 
			@"tell application \"Adium\" \n\
			   set my status message to \"%@\" \n\
			  end tell", status]] autorelease];
		[running executeAndReturnError: &error];
	}
		
	//skype
	running = [[[NSAppleScript alloc] initWithSource: 
			@"\
			tell application \"System Events\"\n\
			 set ret to count (every process whose name is \"Skype\")\n\
			end tell"] autorelease];
	
	if ([[running executeAndReturnError: &error] booleanValue])
	{
		//skype is running
		running = [[[NSAppleScript alloc] initWithSource: 
		[NSString stringWithFormat: 
			@"tell application \"Skype\" \n\
			   send command \"SET PROFILE MOOD_TEXT %@\" script name \"juhu\"\n\
			  end tell", status]] autorelease];
		[running executeAndReturnError: &error];
	}	
}

- (void) _scrollToEntry: (int) index
{
	// scrolls the webview to entry with index

	// but first, if window is too small -> make it larger
	if ([mainWindow frame].size.height <= 300)
	{
		NSRect new = [mainWindow frame];
		new.size.height = 300;
		[mainWindow setFrame: new display: YES animate: YES];
	}
	
	// a more direct way should exist! loadRequest: ?
	[[htmlView windowScriptObject] evaluateWebScript: [NSString stringWithFormat: @"location.href='#entry%i';", index]];
	
	// expand
	[[htmlView windowScriptObject] evaluateWebScript: [NSString stringWithFormat: @"document.getElementById('entryNumber%i').className = \"entryClicked\";", index]];
}

- (void) _timerSetup
{
	//set up timer, check if timer time in defaults different, kick off the timer
	NSTimeInterval aTime;
	
	if ([[[NSUserDefaults standardUserDefaults] valueForKey: @"refreshTime"] intValue] == 0)
		aTime = 30;
	else if ([[[NSUserDefaults standardUserDefaults] valueForKey: @"refreshTime"] intValue] == 1)
		aTime = 60;
	else if ([[[NSUserDefaults standardUserDefaults] valueForKey: @"refreshTime"] intValue] == 2)
		aTime = 300;
	else if ([[[NSUserDefaults standardUserDefaults] valueForKey: @"refreshTime"] intValue] == 3)
		aTime = 15*60;
	else if ([[[NSUserDefaults standardUserDefaults] valueForKey: @"refreshTime"] intValue] == 4)
		aTime = 32*60;
	else if ([[[NSUserDefaults standardUserDefaults] valueForKey: @"refreshTime"] intValue] == 5)
		aTime = 60*60;
	else
		aTime = 60*60*24;
		
	if (aTime != timerTime)
	{
		timerTime = aTime;
		if (theTimer != nil)
			[theTimer invalidate];
		theTimer = [NSTimer scheduledTimerWithTimeInterval: timerTime
			target: self selector: @selector ( refreshClick:)  userInfo: nil repeats: YES];
	}
}

- (void) _openInSafari: (id) sender
{
	//only called by web view context menu item open in browser (created in ui delegate...)
	[[NSWorkspace sharedWorkspace] openURL: [sender representedObject]];
}

- (void) _setStreamSelector
{
	NSString *oldStreamSelector = nil;
	if (streamSelector != nil)
		 oldStreamSelector = [NSString stringWithString: streamSelector];

	[streamSelector release];
	//sets the stream selector to the one in user defaults

	if ([[NSUserDefaults standardUserDefaults] integerForKey: @"streamSelect"] == 0)
		streamSelector = [[NSString stringWithString: kSmcPrivate] retain];
	else
		streamSelector = [[NSString stringWithString: kSmcPublic] retain];

}

- (void) _checkDefaults
{
	//set up initial defaults.
	NSUserDefaults *def = [NSUserDefaults standardUserDefaults];
	if ([def objectForKey: @"streamSelect"] == nil)
		[def setInteger: 1  forKey: @"streamSelect"];
	if ([def objectForKey: @"refreshTime"] == nil)
		[def setInteger: 2 forKey: @"refreshTime"];
	if ([def objectForKey: @"errorMsg"] == nil)
		[def setBool: NO forKey: @"errorMsg"];
	if ([def objectForKey: @"playSound"] == nil)
		[def setBool: NO forKey: @"playSound"];
	if ([def objectForKey: @"soundSelect"] == nil)
		[def setValue: @"Ping" forKey: @"soundSelect"];
	if ([def objectForKey: @"onlogin"] == nil)
		[def setBool: NO forKey: @"onlogin"];
	if ([def objectForKey: @"hotkey"] == nil)
		[def setBool: YES forKey: @"hotkey"];
	if ([def objectForKey: @"autoupdate"] == nil)
		[def setBool: YES forKey: @"autoupdate"];
	if ([def objectForKey: @"postchatclient"] == nil)
		[def setBool: NO forKey: @"postchatclient"];
	if ([def objectForKey: @"smallFootprint"] == nil)
		[def setBool: NO forKey: @"smallFootprint"];	
	if ([def objectForKey: @"themeIndex"] == nil)
		[def setInteger: 0 forKey: @"themeIndex"];	
}

- (BOOL) _trySettingUsernameAndPersonalKeyFromKeychain
{
	//trying to set username and key from chain.
	
	NSURLCredential *jaikuCredential = [[[NSURLCredential alloc] init] autorelease];
	
	//get shared credential storage...
	NSURLCredentialStorage *cstore = [NSURLCredentialStorage sharedCredentialStorage];
	
	//try default for api.jaiku.com, return NO if there is none
	if (jaikuCredential = [cstore defaultCredentialForProtectionSpace: [[[NSURLProtectionSpace alloc] initWithHost: @"api.jaiku.com" 
		port: 0 protocol:@"http" realm: nil authenticationMethod: NSURLAuthenticationMethodDefault] autorelease]])
	{
		//yes there it is! init jaiku api object
		[jaiku setUsername: [jaikuCredential user]];
		[jaiku setPersonalKey: [jaikuCredential password]];
	}
	else 
		return NO;

	return YES;
}

- (void) _saveCredentialsInKeychain
{
	//saving username and personalKey in Keychain

	NSURLCredential *jaikuCredential = [[NSURLCredential alloc] initWithUser: [jaiku username]
		password: [jaiku personalKey] 
		persistence: NSURLCredentialPersistencePermanent];

	//get shared credential storage...
	NSURLCredentialStorage *cstore = [NSURLCredentialStorage sharedCredentialStorage];

	[cstore setCredential: jaikuCredential 
		forProtectionSpace: [[[NSURLProtectionSpace alloc] initWithHost: @"api.jaiku.com" 
		port: 0 protocol:@"http" realm: nil authenticationMethod: NSURLAuthenticationMethodDefault] autorelease]];

	//make default
	[cstore setDefaultCredential: jaikuCredential forProtectionSpace: [[[NSURLProtectionSpace alloc] initWithHost: @"api.jaiku.com" 
		port: 0 protocol:@"http" realm: nil authenticationMethod: NSURLAuthenticationMethodDefault] autorelease]];
	
	[jaikuCredential release];
}

/*
	Delegates
*/
/* main window */

- (void) windowPreferenceButtonClicked: (id) sender
{
	// main window delegated when prefbutton clicked
	[self showPrefs: sender];
} 

- (void) windowRefreshButtonClicked: (id) sender
{
	// main window delegated when refresh button
	[self refreshClick: sender];
}

- (void) windowHomeButtonClicked: (id) sender
{
	// main window delegated when home button
	[self jaikuClick: nil];
}


- (void)windowDidResize:(NSNotification *)aNotification
{
	//main window delegated
	//force refresh	
	[mainWindow display];
}

- (void)windowDidResignKey:(NSNotification *)notification
{
	//main window delegated
	[mainWindow makeFirstResponder: messageField];
	[[mainWindow contentView] setNeedsDisplay: YES];
}

- (void)windowDidBecomeKey:(NSNotification *)aNotification
{
	if ([[statusItem toolTip] isEqualToString: kSmcStatusTooltipNew])
	{
		[statusItem setImage: [NSImage imageNamed: @"bar_none"]];
		[statusItem setToolTip: kSmcStatusTooltipNone];
	}
	
	[postBox setNeedsDisplay: YES];
}

/* jaiku api update delegates */

- (void) stream: (NSString*) stream updatedWithResult: (id) result andIndexSetForNewPosts: (id) _indexSet 	
{	
	// update done successfully

	// no reason to scroll around if nothing new
	// so save scroll position (restore after load finished, look at delegates)
	if ([_indexSet count] != 0)
		savedScrollPosition = NSMakePoint(0, 0);
	else
		savedScrollPosition = [[[[[[htmlView mainFrame] frameView] documentView] enclosingScrollView] contentView] bounds].origin;
	
	[[htmlView mainFrame] loadArchive: 
		[smcJaikuHTML webArchiveWithJaikuAPIObject: jaiku forStream: stream hideChannels: NO collapsed: collapsedEntries expanded: expandedEntries]];

	[(smcWindow *) mainWindow stopProgressAnimation];
	
	// update and make noise if new
	if ([_indexSet count] != 0)
	{
		//update growl, the newest three
		[self _growlUpdateWithIndexSet: _indexSet];
				
		// set statusbar icon and tooltip
		[statusItem setImage: [NSImage imageNamed: @"bar_update"]];
		[statusItem setToolTip: kSmcStatusTooltipNew];
		
		// play sound if wanted
		if ([[NSUserDefaults standardUserDefaults] boolForKey: @"playSound"])
			[[NSSound soundNamed: [[NSUserDefaults standardUserDefaults] stringForKey: @"soundSelect"]] play];
			
	} else
	{
		[statusItem setImage: [NSImage imageNamed: @"bar_none"]];
		[statusItem setToolTip: kSmcStatusTooltipNone];
	}
}

- (void) streamUpdateFailedWithError: (NSError *) error
{
	[(smcWindow *) mainWindow stopProgressAnimation];
	// set statusbar icon
	[statusItem setImage: [NSImage imageNamed: @"bar_error"]];
	[statusItem setToolTip: kSmcStatusTooltipError];
	
	if ([[[NSUserDefaults standardUserDefaults] valueForKey: @"errorMsg"] boolValue])
	{
		//order out window and show it!
		[NSApp activateIgnoringOtherApps: YES];
		[mainWindow makeKeyAndOrderFront: self];
		
		NSRunAlertPanel([[error userInfo] objectForKey: @"errorAbstract"], [[error userInfo] objectForKey: @"errorText"], @"Ok", nil,nil);
	}

	//show error page if nothing else loaded
	if ([jaiku stream: streamSelector] == nil)
		[[htmlView mainFrame] loadHTMLString: [smcJaikuHTML errorPage] baseURL: nil];
}
/* end jaiku delegates */


/* message field delegate */
- (void)controlTextDidChange:(NSNotification *)aNotification
{
	//delegated from message text view, show number of characters left, and block string if 140 is achieved
	//BUT ONLY if not commenting!

	if (0 == [[messageField stringValue] length])
		[sendButton setEnabled: NO];
	else if (![sendButton isEnabled])
		[sendButton setEnabled: YES];
	
	if (!comment)
	{		
		if (140 < [[messageField stringValue] length])
			[messageField setStringValue: [[messageField stringValue] substringToIndex: 140]];
		
		[charsLeft setStringValue: [NSString stringWithFormat: @"%i left", 140 - [[messageField stringValue] length]]];
	} 
	[postBox setNeedsDisplay: YES];
}

- (BOOL)control:(NSControl *)control textShouldBeginEditing:(NSText *)fieldEditor
{
	//NSLog(@"MHHHHHHHHH");
	return YES;
}

- (void)controlTextDidBeginEditing:(NSNotification *)aNotification
{
	[postBox setNeedsDisplay: YES];
}

- (void)controlTextDidEndEditing:(NSNotification *)aNotification
{
	[postBox setNeedsDisplay: YES];
}

/* WebView Delegate (gui, policy...) */

- (NSArray *)webView:(WebView *)sender contextMenuItemsForElement:(NSDictionary *)element defaultMenuItems:(NSArray *)defaultMenuItem
{
	//WebView UI delagated, right click.
	//open in safari on selected html link, so if selected, try to find link, if there is one, return Open in Safari item
	if ([[element objectForKey: @"WebElementIsSelected"] intValue] == 1)
	{
		//something is selected.
		//get range of selection
		NSRange range = NSMakeRange([[htmlView selectedDOMRange] startOffset],[[htmlView selectedDOMRange] endOffset]-[[htmlView selectedDOMRange] startOffset]);
		
		//try to find a link, take selection and make url
		NSString *tmp = [NSString stringWithString: [[[element objectForKey: @"WebElementDOMNode"] nodeValue]substringWithRange: range]];
			
		//try to make it an url if it works, return open in safari.
		NSURL *url;
		if (url = [NSURL URLWithString: tmp])
		{
			NSMenuItem *item = [[NSMenuItem alloc] initWithTitle: @"Open in Browser"
				action: @selector (_openInSafari:) keyEquivalent: @""];
			[item setTarget: self];
			[item setRepresentedObject: url];
			return [NSArray arrayWithObject: [item autorelease]];
		}
	}
	
	return nil;
}

- (WebView *)webView:(WebView *)sender createWebViewWithRequest:(NSURLRequest *)request
{
	//webview ui delegated
	//called when an entry is clicked
	//so set icon to normal
	
	if ([[statusItem toolTip] isEqualToString: kSmcStatusTooltipNew])
	{
		[statusItem setImage: [NSImage imageNamed: @"bar_none"]];
		[statusItem setToolTip: kSmcStatusTooltipNone];
	}
	
	return nil;
}

- (void)webView:(WebView *)sender decidePolicyForNavigationAction:(NSDictionary *)actionInformation 
	request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id<WebPolicyDecisionListener>)listener
{
	//WebView Policy delagated, open links in browser, not in view...

	//if link, do it
	if ([[actionInformation objectForKey: @"WebActionNavigationTypeKey"] intValue] == WebNavigationTypeLinkClicked)
	{	
		//figuring out what exactly todo
		if (([[[request URL] host] isEqualToString: @"removeContact"]) || ([[[request URL] host] isEqualToString: @"addContact"]))
		{
			// add or remove contact
			int index = [[[request URL] port] intValue];
			[self addContactWithNick: [[[request URL] path] substringFromIndex: 1]
				andFirst: [[[jaiku stream: streamSelector] objectAtIndex: index] objectForKey: kSmcEntryKeyFirstName]];
		}
		else if ([[[request URL] host] isEqualToString: @"deletePost"])
			[self deletePostWithURLPathString: [[[request URL] path] substringFromIndex: 1] 
				andFragment: [[request URL] fragment]];
		else if ([[[request URL] host] isEqualToString: @"comment"])
			[self commentToPostNumber: [[[request URL] port] intValue]];
		else
			[[NSWorkspace sharedWorkspace] openURL: [request URL]];
		[listener ignore];
	} else
		[listener use];
}

- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame
{
	// webview load delegated
	// restore scrollposition (saved in streamUpdate:)
	[htmlView setHidden: NO];
	[[[[[[htmlView mainFrame] frameView] documentView] enclosingScrollView] documentView] scrollPoint: savedScrollPosition];
}

/* NSApp */

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{	
	//app delegated
	//if not hidden (most likely because on login), bring to front
	if (![NSApp isHidden])
	{
		[NSApp activateIgnoringOtherApps: YES];
		[mainWindow makeKeyAndOrderFront: self];
	}
	
	/* changed behaviour in 098: no initial credential screen
	
	//try to set username and key from keychain, if not possible open cred sheet
	if (![self _trySettingUsernameAndPersonalKeyFromKeychain])
		[self showCredentials: self];
	//NSLog(@"credentials done");		
	 
	*/
	[self _trySettingUsernameAndPersonalKeyFromKeychain];
	
	// okay, try to load contacts
	[(smcWindow *) mainWindow startProgressAnimation];
	[jaiku updateFriendListSynchronously: YES];
	
	[(smcWindow *) mainWindow stopProgressAnimation];
		//NSLog(@"start update");		
	//run update
	[self refreshClick: self];

	//[[mainWindow contentView] setNeedsDisplay: YES];
}

- (void)applicationWillTerminate:(NSNotification *)aNotification
{
	//delegated from app, cleaning up...
	
	//fix unseen bug (icon remains after quit)
	[[NSStatusBar systemStatusBar] removeStatusItem: statusItem]; 
}

/* growl delegate */

- (NSDictionary *) registrationDictionaryForGrowl
{
	//called by growl to register app
	
	//built dictionary
	//two notes: new posts, new comments
	NSArray *allNotifications = [NSArray arrayWithObjects: 
		grNOTIFICATION_NEW_POST, grNOTIFICATION_NEW_COMMENT, nil];
		
	NSDictionary *tmp = [NSDictionary dictionaryWithObjectsAndKeys:
		allNotifications, GROWL_NOTIFICATIONS_ALL,
		allNotifications, GROWL_NOTIFICATIONS_DEFAULT, nil];
	
	return tmp;
}

- (void) growlNotificationWasClicked: (id) clickContext
{
	//called when note is clicked
	[statusItem setImage: [NSImage imageNamed: @"bar_none"]]; 
	[statusItem setToolTip: kSmcStatusTooltipNone];

	//select item if it can be found...
	int number = [[clickContext objectForKey: @"item"] intValue];
	NSString *pid = [clickContext objectForKey: @"id"];
	
	if ([pid isEqualToString: [[[jaiku stream: streamSelector] objectAtIndex: number] objectForKey: kSmcEntryKeyID]])
	{
		//okay it is still with the right number
		//scroll to it
		[self _scrollToEntry: number];		
		//show window
		[NSApp activateIgnoringOtherApps: YES];
		[mainWindow makeKeyAndOrderFront: self];
	} else
	{
		//roll through complete list
		for (int i = 0; i<[[jaiku stream: streamSelector] count]; i++)
			if ([pid isEqualToString: [[[jaiku stream: streamSelector] objectAtIndex: i] objectForKey: kSmcEntryKeyID]])
			{
				//found!
				[self _scrollToEntry: i];
				
				//show window
				[NSApp activateIgnoringOtherApps: YES];
				[mainWindow makeKeyAndOrderFront: self];
				
				return;
			}
		//not found, launch webpage
		[[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: pid]];
	}		
}

/*
	Actions
*/
- (IBAction)showPrefs:(id)sender
{
	//show preference sheet using NSApp

	//cancel updates
	doingUpdate = NO;
	//[spinWheel stopAnimation: self];

	[NSApp beginSheet: prefWindow
		modalForWindow: (NSWindow *) mainWindow
		modalDelegate: nil 
		didEndSelector: nil 
		contextInfo: nil];
		
	[NSApp runModalForWindow: prefWindow];
    [NSApp endSheet: prefWindow];
    [prefWindow orderOut: self];
}

- (IBAction)donePrefs:(id)sender
{
	//preference sheet okay click, close sheet
	[NSApp stopModal];
	
	//new preference, maybe stream change
	[self _setStreamSelector];
	
	//check on timer
	[self _timerSetup];
	
	[[mainWindow contentView] setNeedsDisplay: YES];
	
	//kickoff update
	[self refreshClick: nil];
}

- (IBAction) showCredentials: (id) sender
{
	//show credentials window

	if ([jaiku username] != nil)
		[[credential cellAtIndex: 0] setStringValue: [jaiku username]];
	if ([jaiku personalKey] != nil)
		[[credential cellAtIndex: 1] setStringValue: [jaiku personalKey]];
	
	[NSApp beginSheet: credWindow
			modalForWindow: mainWindow
			modalDelegate: nil 
			didEndSelector: nil 
			contextInfo: nil];
		
	[NSApp runModalForWindow: credWindow];
	[NSApp endSheet: credWindow];
	[credWindow orderOut: self];
}

- (IBAction) doneCredential: (id) sender
{
	//credential sheet
	if ([sender tag] == 0)
	{
		// next/done button

		int selectedTab = [credentialTabView indexOfTabViewItem: [credentialTabView selectedTabViewItem]];

		switch (selectedTab)
		{
			case 0: // the info tab, user wants to start authentication
				NSLog(@"start authentication process");
				
				// show progress tab
				[credentialTabView selectTabViewItemAtIndex: 1];
				
				// perform request token
				[jaiku requestToken];
				
				// show tab for web site
				[credentialTabView selectTabViewItemAtIndex: 2];
				
				break;
			case 2: // open the browser 
				NSLog(@"browser open");
				
				// show next tab
				[credentialTabView selectTabViewItemAtIndex: 3];
				break;
			case 4: // already account
				break;
			case 5: // the non-oauth account tab (to be removed once oauth is flying)
				[jaiku setUsername: [[credential cellAtIndex: 0] stringValue]];
				[jaiku setPersonalKey: [[credential cellAtIndex: 1] stringValue]];
				[self _saveCredentialsInKeychain];
				[self refreshClick: self];
				[NSApp stopModal];
				break;
			break;
		}
	} else if ([sender tag] == 1)
		[NSApp stopModal]; //cancel, just let it go..
		
	[[mainWindow contentView] setNeedsDisplay: YES];
	[postBox setNeedsDisplay: YES];
}

- (IBAction)showIcons: (id) sender
{
	//show icon sheet or remove icon, depending on if there is an icon
	
	if ([sender tag] == 0)
	{
		//add icon
		[NSApp beginSheet: iconWindow
			modalForWindow: [sender window]
			modalDelegate: nil 
			didEndSelector: nil 
			contextInfo: nil];
		
		[NSApp runModalForWindow: iconWindow];
		[NSApp endSheet: iconWindow];
		[iconWindow orderOut: self];	
	} else if ([sender tag] == 1)
	{
		//remove icon
		[sender setTag: 0];
		[sender setImage: [NSImage imageNamed: @"icon_add"]];
		[sender setToolTip: @"Add icon to post"];
		[plusIconButton setTitle: @"icon"];
		
		NSRect rect = [messageField frame];
		//set x value - width of iconview..
		rect.origin.x = rect.origin.x - [postIcon frame].size.width;
		rect.size.width = rect.size.width + [postIcon frame].size.width;		
		//reset frames
		[messageField setFrame: rect];
	
		//hide icon
		[postIcon setHidden: YES];
		
		//remove icon internally
		postIconIndex = kSmcIconNone;
	} else if ([sender tag] == 2)
	{
		//cancel commenting
		
		//commenting flag
		comment = NO;
		
		//reset icon
		[sender setTag: 0];
		[sender setImage: [NSImage imageNamed: @"icon_add"]];
		[sender setToolTip: @"Add icon to post"];
		[plusIconButton setTitle: @"icon"];
		
		//set from label, show location and chars left
		[fromLabel setStringValue: @"from"];
		[location setHidden: NO];
		[charsLeft setHidden: NO];
		[messageField setStringValue: @""];
	}
	
	//strange, but it works
	[postBox display];
	[postBox setNeedsDisplay: YES]; 
}

- (IBAction)doneIcons: (id) sender
{
	//icon sheet icon click, close sheet
	[NSApp stopModal];
	
	[mainWindow makeFirstResponder: messageField];
	
	//change icon and tag
	[plusIconButton setTag: 1];
	[plusIconButton setImage: [NSImage imageNamed: @"icon_trash"]];
	[plusIconButton setToolTip: @"Remove icon from post"];
	
	//show icon
	[postIcon setImage: [NSImage imageNamed: [NSString stringWithFormat: @"%i", [sender tag]]]];
	[postIcon setHidden: NO];
	
	//adjust message box
	NSRect rect = [messageField frame];
	//set x value + width of iconview and shorten it
	rect.origin.x = rect.origin.x + [postIcon frame].size.width;
	rect.size.width = rect.size.width - [postIcon frame].size.width;
	[messageField setFrame: rect];

	//set icon up internally
	postIconIndex = [sender tag];
}

- (IBAction) refreshClick: (id) sender
{
	//refresh streams
	
	//growler setup, 
	//for example on login growl might not be started yet, so check and setup
	[self _growlConfig];
	//NSLog(@"growl done");		
	//save state of each entry (expanded/collapsed)
	NSString *entry = nil;
	[collapsedEntries removeAllObjects];
	[expandedEntries removeAllObjects];

	/* ugly hack for stream change */
	if (([jaiku stream: streamSelector] != nil) && (sender != nil))
		for (int i = 0; i < [[jaiku stream: streamSelector] count]; i++)
		{
			 entry = [NSString stringWithFormat: @"entryNumber%i",i];
			if ([[[[htmlView windowScriptObject] valueForKey: entry] valueForKey: @"className"] isEqualToString: @"entry"])
				[collapsedEntries addObject: [NSString stringWithString: [[[jaiku stream: streamSelector] objectAtIndex: i] objectForKey: kSmcEntryKeyID]]];
			else
				[expandedEntries addObject: [NSString stringWithString: [[[jaiku stream: streamSelector] objectAtIndex: i] objectForKey: kSmcEntryKeyID]]];
		}

	//start spin
	[(smcWindow *) mainWindow startProgressAnimation];
	
	//reload contacts
	[jaiku updateFriendListSynchronously: NO];		
		//NSLog(@"fl done");		
	//run update
	if (![jaiku beginUpdateForStream: streamSelector])
		[(smcWindow *) mainWindow stopProgressAnimation];
}

- (IBAction) statusItemClick: (id) sender
{
	//toggle status bar item
	[statusItem setImage: [NSImage imageNamed: @"bar_none"]];
	[statusItem setToolTip: kSmcStatusTooltipNone];

	//on status bar click - show/hide
	if ([mainWindow isVisible])
	{
		//is onscreen, so if behind something order out, else: hide!
		if ([(NSWindow *) mainWindow isKeyWindow])
		{
			[mainWindow performClose: self];
		} else
		{
			[NSApp activateIgnoringOtherApps: YES];
		}
	}
	else
	{	
		[NSApp activateIgnoringOtherApps: YES];
		[mainWindow makeKeyAndOrderFront: self];
	}
}

- (IBAction) quitClicked: (id) sender
{
	[[sender window] close];
	[NSApp terminate: nil];
}

- (IBAction) sendMessage: (id) sender
{
	//send the actual post
	NSError *anError = nil;
	
	//post or comment?
	if (!comment)
	{
		//send post!
		[(smcWindow *) mainWindow startProgressAnimation];
		anError = [jaiku postMessage: [messageField stringValue] withLocation: [location stringValue] 
			andIcon: postIconIndex generated: NO];
		[(smcWindow *) mainWindow stopProgressAnimation];
		
		if (anError == nil)
		{
			//post to chat clients
			if ([[NSUserDefaults standardUserDefaults] boolForKey: @"postchatclient"])
				[self _updateChatClients: [messageField stringValue]];
			
			//everything good, so reset message bubble.
			[messageField setStringValue: @""];
			[postIcon setHidden: YES];
			[charsLeft setStringValue: @"140 left"];
			[sendButton setEnabled: NO];
			
			[plusIconButton setTag: 0];
			[plusIconButton setImage: [NSImage imageNamed: @"icon_add"]];
			[plusIconButton setToolTip: @"Add icon to post"];
					
			NSRect rect = [messageField frame];
			if (postIconIndex != kSmcIconNone)
			{
				//set x value - width of iconview..
		
				rect.origin.x = rect.origin.x - [postIcon frame].size.width;
				rect.size.width = rect.size.width + [postIcon frame].size.width;		
				//reset frames
				[messageField setFrame: rect];
		
				//hide icon
				[postIcon setHidden: YES];
			
				//remove icon internally
				postIconIndex = kSmcIconNone;	

			}				
			
			//do update
			[self refreshClick: self];
		}
		else
		{
			//showing alert panels with errors if desired
			if ([[[NSUserDefaults standardUserDefaults] valueForKey: @"errorMsg"] boolValue])
					NSRunAlertPanel( @"Posting message to jaiku failed", [[anError userInfo] objectForKey: @"errorText"], @"Ok", nil,nil);
		}
	} else
	{
		//send comment
		[(smcWindow *) mainWindow startProgressAnimation];
		anError = [jaiku postComment: [messageField stringValue] to: commentTo];
		[(smcWindow *) mainWindow stopProgressAnimation];
		
		if (anError == nil)
		{
			//reset it all, cause no errors
			//commenting flag
			comment = NO;
			
			//reset icon
			[plusIconButton setTag: 0];
			[plusIconButton setImage: [NSImage imageNamed: @"icon_add"]];
			[plusIconButton setToolTip: @"Add icon to post"];
			[plusIconButton setTitle: @"icon"];
			
			//set from label, show location and chars left
			[fromLabel setStringValue: @"from"];
			[location setHidden: NO];
			[charsLeft setHidden: NO];
			
			//reset message bubble.
			[messageField setStringValue: @""];
			[charsLeft setStringValue: @"140 left"];
			[sendButton setEnabled: NO];
			
			//do update
			[self refreshClick: self];
		} else
		{
			//showing alert panels with errors if desired
			if ([[[NSUserDefaults standardUserDefaults] valueForKey: @"errorMsg"] boolValue])
					NSRunAlertPanel( @"Posting comment to jaiku failed", [[anError userInfo] objectForKey: @"errorText"], @"Ok", nil,nil);
		}
	}

	[postBox setNeedsDisplay: YES];
}

- (IBAction)jaikuClick: (id) sender
{
	if (([sender tag] == 0) && ([jaiku username] != nil))
		[[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: [NSString stringWithFormat: @"http://%@.jaiku.com", [jaiku username]]]];
	else if ([sender tag] == 3)
		[[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: @"http://juhu-mac.blogspot.com"]];
	else if ([sender tag] == 4)
		[[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: @"http://api.jaiku.com"]];
	else
		[[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: @"http://jaiku.com"]];
}

- (IBAction) playSound: (id) sender
{
	//play sound selected and update user defaults
	[[NSUserDefaults standardUserDefaults] setValue: [sender title] forKey: @"soundSelect"];
	[[NSSound soundNamed: [sender title]] play];
}

- (IBAction) setupLoginStartup: (id) sender
{
	//setup run on login
	NSString *loginWindowPlistPath = [@"~/Library/Preferences/loginwindow.plist" stringByExpandingTildeInPath]; 
	NSMutableDictionary *loginWindowPrefsDictionary = [NSMutableDictionary dictionaryWithContentsOfFile:loginWindowPlistPath];
	NSMutableArray *launchItems = [NSMutableArray arrayWithArray:[loginWindowPrefsDictionary valueForKey:@"AutoLaunchedApplicationDictionary"]];
	NSEnumerator *enumerator = [launchItems objectEnumerator];
	id application;
	
	//prior versions added a new entrie each time this method was called, 
	//so to clean the file, just delete all juhu entries always
	//and add one new if juhu is autologin...
	
	//delete! all juhu(s) from list
	while ( application = [enumerator nextObject] ) 
	{
		if ( [[[application valueForKey:@"Path"] lastPathComponent] isEqualToString:@"juhu.app"] ) 
		{
			[launchItems removeObject:application];
			[loginWindowPrefsDictionary setObject:launchItems forKey:@"AutoLaunchedApplicationDictionary"];
			[loginWindowPrefsDictionary writeToFile:loginWindowPlistPath atomically:YES];
		}
	}

	//add entry if login startup desired
	if ([[[[NSUserDefaultsController sharedUserDefaultsController] values] valueForKey:@"onlogin"] boolValue]) 
	{
		NSString *fullPath = [NSString stringWithFormat: @"%@", [[NSBundle mainBundle] bundlePath]];
		[launchItems addObject: [NSMutableDictionary dictionaryWithObjectsAndKeys: 
			fullPath, @"Path",
			[NSNumber numberWithBool: YES], @"Hide", NULL]];
		[loginWindowPrefsDictionary setObject:launchItems forKey:@"AutoLaunchedApplicationDictionary"];
		[loginWindowPrefsDictionary writeToFile:loginWindowPlistPath atomically:YES];
	}
}

- (void) commentToPostNumber: (int) index
{
	//comment button click
	//set comment flag
	comment = YES;
	
	//save id url of post to comment to
	[commentTo release];
	commentTo = [[NSURL URLWithString: 
		[[[jaiku stream: streamSelector] objectAtIndex: index] objectForKey: kSmcEntryKeyID]] retain];
		
	//if in icon mode, hide icon and make textfield larger
	if (postIconIndex != kSmcIconNone)
	{
		NSRect rect = [messageField frame];
		//set x value - width of iconview..
		rect.origin.x = rect.origin.x - [postIcon frame].size.width;
		rect.size.width = rect.size.width + [postIcon frame].size.width;		
		//reset frames
		[messageField setFrame: rect];
		//hide icon
		[postIcon setHidden: YES];
		//remove icon internally
		postIconIndex = kSmcIconNone;	
	}
	
	//hide location and chars left
	[location setHidden: YES];
	[charsLeft setHidden: YES];
	
	//put info into from field
	[fromLabel setStringValue: [NSString stringWithFormat: @"to %@: %@",
		[[[jaiku stream: streamSelector] objectAtIndex: index] objectForKey: kSmcEntryKeyFirstName],
		[[[jaiku stream: streamSelector] objectAtIndex: index] objectForKey: kSmcEntryKeyTitle]]];
	
	//change add icon button to trash comment button
	[plusIconButton setTag: 2];
	[plusIconButton setImage: [NSImage imageNamed: @"icon_trash"]];
	[plusIconButton setTitle: @"comment"];
	[plusIconButton setToolTip: @"cancel comment"];	
	
	[messageField setNeedsDisplay: YES];
	[mainWindow makeFirstResponder: messageField];
}

- (IBAction) hotkeySetup: (id) sender
{
	//setup hotkey please remember: this is carbon, not cocoa!
	if ([[NSUserDefaults standardUserDefaults] boolForKey: @"hotkey"])
	{
		//register hotkey
		EventHotKeyID keyID;
		EventTypeSpec eventType;
		eventType.eventClass = kEventClassKeyboard;
		eventType.eventKind = kEventHotKeyPressed;
		
		//install handler
		InstallApplicationEventHandler(hotkeyHandler, 1, &eventType, self, NULL);
		
		//setup hotkey
		keyID.signature = 'hkju';
		keyID.id = 1;
		
		//register event
		RegisterEventHotKey (38, cmdKey+optionKey, keyID, GetApplicationEventTarget(), 0, &keyRef);
	} else if (keyRef != NULL)
	{
		//key register but not wanted -> unregister
		UnregisterEventHotKey(keyRef);	
	}
}

- (IBAction) changedTheme: (id) sender
{
	//refresh window after theme change
	[mainWindow display];
}

- (void) deletePostWithURLPathString: (NSString *) urlString andFragment: (NSString *) fragment
{
	//delete post
	//really delete?
	if (NSRunAlertPanelRelativeToWindow(@"Delete",
		@"Do you really want to delete this post?", 
			@"No" , @"Yes", nil, mainWindow) == 0)
	{
		NSURL *url = nil;
		if (fragment == nil)
			url = [NSURL URLWithString: urlString];
		else
			url = [NSURL URLWithString: [NSString stringWithFormat: @"%@#%@",urlString, fragment]]; 
	
		if ([jaiku deletePostOrCommentWithURL: url] != nil)
		{
			//error
			NSRunAlertPanel( @"Deleting jaiku post failed.", @"Sorry, juhu could not delete the post, something went wrong.", @"Ok", nil,nil);
		}

		[self refreshClick: self];
	}
}

- (void) addContactWithNick: (NSString *) nick andFirst: (NSString *) first
{
	//remove or add
	if([jaiku isFriend: nick])
	{
		//really delete?
		if (NSRunAlertPanelRelativeToWindow(@"Really Remove",
		[NSString stringWithFormat: @"Do you really want to remove %@ (%@) from your contacts?", first, nick], 
			@"No" , @"Yes", nil, mainWindow) == 0)
		{
			[jaiku removeFriend: nick];
		}
	}
	else
	{
		//really add?
		if (NSRunAlertPanelRelativeToWindow(@"Really Add",
		[NSString stringWithFormat: @"Do you really want to add %@ (%@) to your contacts?", first, nick], 
			@"No" , @"Yes", nil, mainWindow) == 0)
		{
			[jaiku addAsFriend: nick];
		}
	}
	
	[self refreshClick: nil];
}

- (void) awakeFromNib
{
	//inits
	streamSelector = kSmcPublic;
	collapsedEntries = [[NSMutableSet set] retain];
	expandedEntries = [[NSMutableSet set] retain];
	doingUpdate = NO;
	comment = NO;
	jaiku = [[smcJaikuAPI alloc] initWithUsername: @"" andPersonalKey: @""];
	[jaiku setDelegate: self];
	savedScrollPosition = NSMakePoint(0, 0);

	theTimer = nil;
	commentTo = nil;
	timerTime = 0;

	postIconIndex = kSmcIconNone;
	keyRef = NULL;

	//check preferences and reset if neccessary
	[self _checkDefaults];	
	
	//setup autostart entry
	[self setupLoginStartup: self];
	
	//setup stream selctor
	[self _setStreamSelector];
	
	//set scrollbar
	theScrollbar = [[[smcScroller alloc] initWithFrame: NSMakeRect( 0, 0, 0, 0)] autorelease];	
	[[[[[htmlView subviews] objectAtIndex: 0] subviews] objectAtIndex: 0] setVerticalScroller: theScrollbar];

	//text field no focus ring...
	[messageField setFocusRingType: NSFocusRingTypeNone];
	
	//add status bar icon
	statusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength: -1] retain];
	[statusItem setImage: [NSImage imageNamed: @"bar_none"]];
	[statusItem setToolTip: kSmcStatusTooltipNone];
	[statusItem setTarget: self];
	[statusItem setAction: @selector ( statusItemClick: )];
	[statusItem setHighlightMode: YES];

	//no history needed, just eats up memory
	[[htmlView preferences] setPrivateBrowsingEnabled: YES];
	[htmlView setMaintainsBackForwardList: NO];

	//show loading page
	[[htmlView mainFrame] loadHTMLString: [smcJaikuHTML loadingPage] baseURL: nil];
//	[htmlView setDrawsBackground: NO];
	
	//icon window background
	[iconWindow setBackgroundColor: [NSColor whiteColor]];
	[[iconWindow contentView] setNeedsDisplay: YES];

	//hotkey setup
	[self hotkeySetup: self];

	//set post box
	[(smcRoundedFlatBox *) postBox setBackgroundColor: [NSColor whiteColor]];
	
	//config growl
	[self _growlConfig];
	
	//kick off timer
	[self _timerSetup];
	
	//check for newer version if wanted
	if ([[NSUserDefaults standardUserDefaults] boolForKey: @"autoupdate"])
	{
		updateChecker = [[smcUpdateCheck alloc] init];
		[updateChecker checkForNewerVersion];
	} else
		updateChecker = nil;
}

- (void) dealloc 
{
	[jaiku release];
	[updateChecker release];
	[collapsedEntries release];
	[expandedEntries release];
	[super dealloc];
}
@end
